/*! \file
 * \brief Enter a brief description here.
 *
 * Enter a detailed description here. (optional)
 * \author Author (Ivo Wingelaar)
 * \date Date (2011)
 * \version Version (0.0.1)
 * \copyright Copyright (GNU LGPL v3)
 */

#ifndef __LYNX_GPU_PROGRAMMING_SERVICES_HPP_INCLUDED__
#define __LYNX_GPU_PROGRAMMING_SERVICES_HPP_INCLUDED__

#include "LynxMaterialRendererServices.hpp"

namespace Lynx
{

//! Enumeration for all primitive types there are.
enum E_PRIMITIVE_TYPE
{
    //! All vertices are non-connected points.
    EPT_POINTS=0,
    //! All vertices form a single connected line.
    EPT_LINE_STRIP,
    //! Just as LINE_STRIP, but the last and the first vertex is also connected.
    EPT_LINE_LOOP,
    //! Every two vertices are connected creating n/2 lines.
    EPT_LINES,
    //! After the first two vertices each vertex defines a new triangle.
    //! Always the two last and the new one form a new triangle.
    EPT_TRIANGLE_STRIP,
    //! After the first two vertices each vertex defines a new triangle.
    //! All around the common first vertex.
    EPT_TRIANGLE_FAN,
    //! Explicitly set all vertices for each triangle.
    EPT_TRIANGLES,
    //! After the first two vertices each further tw vetices create a quad with the preceding two.
    EPT_QUAD_STRIP,
    //! Every four vertices create a quad.
    EPT_QUADS,
    //! Just as LINE_LOOP, but filled.
    EPT_POLYGON,
    //! The single vertices are expanded to quad billboards on the GPU.
    EPT_POINT_SPRITES
};

//! Compile target enumeration for the add_high_level_shader_material() method.
enum E_VERTEX_SHADER_TYPE
{
    EVST_VS_1_1 = 0,
    EVST_VS_2_0,
    EVST_VS_2_a,
    EVST_VS_3_0,
    EVST_VS_4_0,
    EVST_VS_4_1,
    EVST_VS_5_0,

    //! This is not a type, but a value indicating how much types there are.
    EVST_COUNT
};


//! Compile target enumeration for the add_high_level_shader_material() method.
enum E_PIXEL_SHADER_TYPE
{
    EPST_PS_1_1 = 0,
    EPST_PS_1_2,
    EPST_PS_1_3,
    EPST_PS_1_4,
    EPST_PS_2_0,
    EPST_PS_2_a,
    EPST_PS_2_b,
    EPST_PS_3_0,
    EPST_PS_4_0,
    EPST_PS_4_1,
    EPST_PS_5_0,

    //! This is not a type, but a value indicating how much types there are.
    EPST_COUNT
};

//! Enum for supported geometry shader types
enum E_GEOMETRY_SHADER_TYPE
{
    EGST_GS_4_0 = 0,

    //! This is not a type, but a value indicating how much types there are.
    EGST_COUNT
};

//! Interface making it possible to set constants for gpu programs every frame.
/** Implement this interface in an own class and pass a pointer to it to one of
the methods in IGPUProgrammingServices when creating a shader. The
on_set_constants method will be called every frame now. */
class ShaderConstantSetCallBack : public virtual Myth::ReferenceCounter
{
public:

    //! Called to let the callBack know the used material (optional method)
    /**
     \code
    class MyCallBack : public IShaderConstantSetCallBack
    {
    	const video::SMaterial *UsedMaterial;

    	on_set_material(const video::SMaterial& material)
    	{
    		UsedMaterial=&material;
    	}

    	on_set_constants(IMaterialRendererServices* services, s32 userData)
    	{
    		services->set_vertex_shader_constant("myColor", reinterpret_cast<f32*>(&UsedMaterial->color), 4);
    	}
    }
    \endcode
    */
    virtual void on_set_material(const Material& material) { }

    //! Called by the engine when the vertex and/or pixel shader constants for an material renderer should be set.
    /**
    Implement the IShaderConstantSetCallBack in an own class and implement your own
    on_set_constants method using the given IMaterialRendererServices interface.
    Pass a pointer to this class to one of the methods in IGPUProgrammingServices
    when creating a shader. The on_set_constants method will now be called every time
    before geometry is being drawn using your shader material. A sample implementation
    would look like this:
    \code
    virtual void on_set_constants(video::IMaterialRendererServices* services, s32 userData)
    {
    	video::IVideoDriver* driver = services->getVideoDriver();

    	// set clip matrix at register 4
    	core::matrix4 worldViewProj(driver->get_transform(video::ETS_PROJECTION));
    	worldViewProj *= driver->get_transform(video::ETS_VIEW);
    	worldViewProj *= driver->get_transform(video::ETS_WORLD);
    	services->set_vertex_shader_constant(&worldViewProj.M[0], 4, 4);
    	// for high level shading languages, this would be another solution:
    	//services->set_vertex_shader_constant("mWorldViewProj", worldViewProj.M, 16);

    	// set some light color at register 9
    	video::SColorf col(0.0f,1.0f,1.0f,0.0f);
    	services->set_vertex_shader_constant(reinterpret_cast<const f32*>(&col), 9, 1);
    	// for high level shading languages, this would be another solution:
    	//services->set_vertex_shader_constant("myColor", reinterpret_cast<f32*>(&col), 4);
    }
    \endcode
    \param services: Pointer to an interface providing methods to set the constants for the shader.
    \param userData: Userdata int which can be specified when creating the shader.
    */
    virtual void on_set_constants(MaterialRendererServices* services, int32_t userData) = 0;
};

//! Interface making it possible to create and use programs running on the GPU.
class GPUProgrammingServices
{
public:

    //! Destructor
    virtual ~GPUProgrammingServices() {}

    //! Adds a new high-level shading material renderer to the VideoDriver.
    /** Currently only HLSL/D3D9 and GLSL/OpenGL are supported.
    \param vertexShaderProgram: String containing the source of the vertex
    shader program. This can be 0 if no vertex program shall be used.
    \param vertexShaderEntryPointName: Name of the entry function of the
    vertexShaderProgram
    \param vsCompileTarget: Vertex shader version the high level shader
    shall be compiled to.
    \param pixelShaderProgram: String containing the source of the pixel
    shader program. This can be 0 if no pixel shader shall be used.
    \param pixelShaderEntryPointName: Entry name of the function of the
    pixelShaderEntryPointName
    \param psCompileTarget: Pixel shader version the high level shader
    shall be compiled to.
    \param geometryShaderProgram: String containing the source of the
    geometry shader program. This can be 0 if no geometry shader shall be
    used.
    \param geometryShaderEntryPointName: Entry name of the function of the
    geometryShaderEntryPointName
    \param gsCompileTarget: Geometry shader version the high level shader
    shall be compiled to.
    \param inType Type of vertices passed to geometry shader
    \param outType Type of vertices created by geometry shader
    \param verticesOut Maximal number of vertices created by geometry
    shader. If 0, maximal number supported is assumed.
    \param callback: Pointer to an implementation of
    IShaderConstantSetCallBack in which you can set the needed vertex,
    pixel, and geometry shader program constants. Set this to 0 if you
    don't need this.
    \param baseMaterial: Base material which renderstates will be used to
    shade the material.
    \param userData: a user data int. This int can be set to any value and
    will be set as parameter in the callback method when calling
    on_set_constants(). In this way it is easily possible to use the same
    callback method for multiple materials and distinguish between them
    during the call.
    \return Number of the material type which can be set in
    SMaterial::MaterialType to use the renderer. -1 is returned if an error
    occured, e.g. if a shader program could not be compiled or a compile
    target is not reachable. The error strings are then printed to the
    error log and can be catched with a custom event receiver. */
    virtual int32_t add_high_level_shader_material(const char* vertexShaderProgram, const char* vertexShaderEntryPointName,
            E_VERTEX_SHADER_TYPE vsCompileTarget,
            const char* pixelShaderProgram,
            const char* pixelShaderEntryPointName,
            E_PIXEL_SHADER_TYPE psCompileTarget,
            const char* geometryShaderProgram,
            const char* geometryShaderEntryPointName = "main",
            E_GEOMETRY_SHADER_TYPE gsCompileTarget = EGST_GS_4_0,
            E_PRIMITIVE_TYPE inType = EPT_TRIANGLES,
            E_PRIMITIVE_TYPE outType = EPT_TRIANGLE_STRIP,
            uint32_t verticesOut = 0,
            ShaderConstantSetCallBack* callback = 0,
            E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
            int32_t userData = 0) = 0;

    //! convenience function for use without geometry shaders
    int32_t add_high_level_shader_material(
        const char* vertexShaderProgram,
        const char* vertexShaderEntryPointName = "main",
        E_VERTEX_SHADER_TYPE vsCompileTarget = EVST_VS_1_1,
        const char* pixelShaderProgram = 0,
        const char* pixelShaderEntryPointName = "main",
        E_PIXEL_SHADER_TYPE psCompileTarget = EPST_PS_1_1,
        ShaderConstantSetCallBack* callback = 0,
        E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
        int32_t userData = 0)
    {
        return add_high_level_shader_material(
                   vertexShaderProgram, vertexShaderEntryPointName,
                   vsCompileTarget, pixelShaderProgram,
                   pixelShaderEntryPointName, psCompileTarget,
                   0, "main", EGST_GS_4_0,
                   EPT_TRIANGLES, EPT_TRIANGLE_STRIP, 0,
                   callback, baseMaterial, userData);
    }

    //! Like IGPUProgrammingServices::add_shader_material(), but loads from files.
    /** \param vertexShaderProgram: Text file handle containing the source
    of the vertex shader program. Set to 0 if no vertex shader shall be
    created.
    \param vertexShaderEntryPointName: Name of the entry function of the
    vertexShaderProgram
    \param vsCompileTarget: Vertex shader version the high level shader
    shall be compiled to.
    \param pixelShaderProgram: Text file handle containing the source of
    the pixel shader program. Set to 0 if no pixel shader shall be created.
    \param pixelShaderEntryPointName: Entry name of the function of the
    pixelShaderEntryPointName
    \param psCompileTarget: Pixel shader version the high level shader
    shall be compiled to.
    \param geometryShaderProgram: Text file handle containing the source of
    the geometry shader program. Set to 0 if no geometry shader shall be
    created.
    \param geometryShaderEntryPointName: Entry name of the function of the
    geometryShaderEntryPointName
    \param gsCompileTarget: Geometry shader version the high level shader
    shall be compiled to.
    \param inType Type of vertices passed to geometry shader
    \param outType Type of vertices created by geometry shader
    \param verticesOut Maximal number of vertices created by geometry
    shader. If 0, maximal number supported is assumed.
    \param callback: Pointer to an implementation of
    IShaderConstantSetCallBack in which you can set the needed vertex and
    pixel shader program constants. Set this to 0 if you don't need this.
    \param baseMaterial: Base material which renderstates will be used to
    shade the material.
    \param userData: a user data int. This int can be set to any value and
    will be set as parameter in the callback method when calling
    on_set_constants(). In this way it is easily possible to use the same
    callback method for multiple materials and distinguish between them
    during the call.
    \return Number of the material type which can be set in
    SMaterial::MaterialType to use the renderer. -1 is returned if an
    error occured, e.g. if a shader program could not be compiled or a
    compile target is not reachable. The error strings are then printed to
    the error log and can be catched with a custom event receiver. */
    virtual int32_t add_high_level_shader_material_from_files(
        Myth::Stream::Input* vertexShaderProgram,
        const char* vertexShaderEntryPointName,
        E_VERTEX_SHADER_TYPE vsCompileTarget,
        Myth::Stream::Input* pixelShaderProgram,
        const char* pixelShaderEntryPointName,
        E_PIXEL_SHADER_TYPE psCompileTarget,
        Myth::Stream::Input* geometryShaderProgram,
        const char* geometryShaderEntryPointName = "main",
        E_GEOMETRY_SHADER_TYPE gsCompileTarget = EGST_GS_4_0,
        E_PRIMITIVE_TYPE inType = EPT_TRIANGLES,
        E_PRIMITIVE_TYPE outType = EPT_TRIANGLE_STRIP,
        uint32_t verticesOut = 0,
        ShaderConstantSetCallBack* callback = 0,
        E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
        int32_t userData = 0) = 0;

    //! convenience function for use without geometry shaders
    int32_t add_high_level_shader_material_from_files(
        Myth::Stream::Input* vertexShaderProgram,
        const char* vertexShaderEntryPointName = "main",
        E_VERTEX_SHADER_TYPE vsCompileTarget = EVST_VS_1_1,
        Myth::Stream::Input* pixelShaderProgram = 0,
        const char* pixelShaderEntryPointName = "main",
        E_PIXEL_SHADER_TYPE psCompileTarget = EPST_PS_1_1,
        ShaderConstantSetCallBack* callback = 0,
        E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
        int32_t userData = 0)
    {
        return add_high_level_shader_material_from_files(
                   vertexShaderProgram, vertexShaderEntryPointName,
                   vsCompileTarget, pixelShaderProgram,
                   pixelShaderEntryPointName, psCompileTarget,
                   0, "main", EGST_GS_4_0,
                   EPT_TRIANGLES, EPT_TRIANGLE_STRIP, 0,
                   callback, baseMaterial, userData);
    }

    //! Adds a new ASM shader material renderer to the VideoDriver
    /** Note that it is a good idea to call IVideoDriver::query_feature() in
    advance to check if the IVideoDriver supports the vertex and/or pixel
    shader version your are using.

    The material is added to the VideoDriver like with
    IVideoDriver::add_material_renderer() and can be used like it had been
    added with that method.
    \param vertexShaderProgram: String containing the source of the vertex
    shader program. This can be 0 if no vertex program shall be used.

    For DX8 programs, the will always input registers look like this: v0:
    position, v1: normal, v2: color, v3: texture cooridnates, v4: texture
    coordinates 2 if available.

    For DX9 programs, you can manually set the registers using the dcl_
    statements.
    \param pixelShaderProgram: String containing the source of the pixel
    shader program. This can be 0 if you don't want to use a pixel shader.
    \param callback: Pointer to an implementation of
    IShaderConstantSetCallBack in which you can set the needed vertex and
    pixel shader program constants. Set this to 0 if you don't need this.
    \param baseMaterial: Base material which renderstates will be used to
    shade the material.
    \param userData: a user data int. This int can be set to any value and
    will be set as parameter in the callback method when calling
    on_set_constants(). In this way it is easily possible to use the same
    callback method for multiple materials and distinguish between them
    during the call.
    \return Returns the number of the material type which can be set in
    SMaterial::MaterialType to use the renderer. -1 is returned if an
    error occured. -1 is returned for example if a vertex or pixel shader
    program could not be compiled, the error strings are then printed out
    into the error log, and can be catched with a custom event receiver. */
    virtual int32_t add_shader_material(const char* vertexShaderProgram = 0,
                                      const char* pixelShaderProgram = 0,
                                      ShaderConstantSetCallBack* callback = 0,
                                      E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
                                      int32_t userData = 0) = 0;

    //! Like IGPUProgrammingServices::add_shader_material(), but loads from files.
    /** \param vertexShaderProgram: Text file containing the source of the
    vertex shader program. Set to 0 if no shader shall be created.
    \param pixelShaderProgram: Text file containing the source of the pixel
    shader program. Set to 0 if no shader shall be created.
    \param callback: Pointer to an IShaderConstantSetCallback object to
    which the on_set_constants function is called.
    \param baseMaterial: baseMaterial
    \param userData: a user data int. This int can be set to any value and
    will be set as parameter in the callback method when calling
    on_set_constants(). In this way it is easily possible to use the same
    callback method for multiple materials and distinguish between them
    during the call.
    \return Returns the number of the material type which can be set in
    SMaterial::MaterialType to use the renderer. -1 is returned if an
    error occured. -1 is returned for example if a vertex or pixel shader
    program could not be compiled, the error strings are then printed out
    into the error log, and can be catched with a custom event receiver. */
    virtual int32_t add_shader_material_from_files(Myth::Stream::Input* vertexShaderProgram,
            Myth::Stream::Input* pixelShaderProgram,
            ShaderConstantSetCallBack* callback = 0,
            E_MATERIAL_TYPE baseMaterial = EMT_SOLID,
            int32_t userData = 0) = 0;
};

} // namespace Lynx

#endif // __LYNX_GPU_PROGRAMMING_SERVICES_HPP_INCLUDED__

